package common.peer;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import se.sics.kompics.address.Address;
import se.sics.kompics.p2p.overlay.OverlayAddress;
import common.configuration.Global;

public class PeerInfo implements Comparable<PeerInfo> {
	PeerAddress peeraddress;
	ArrayList<PeerAddress> parents = new ArrayList<PeerAddress>();
	HashMap<PeerAddress, Integer> parentPLs = new HashMap<PeerAddress, Integer>();
	Integer hopcount;
	Integer hopcount2;
	Integer age;
	Integer parentPreference;
	boolean testVar;  // USED FOR TESTING PURPOSES
	
	public PeerInfo(PeerAddress peeraddress, ArrayList<PeerAddress> parents)
	{
		this.peeraddress = peeraddress;
		this.parents = parents;
		if(peeraddress.isSource() == false)
		{
			hopcount = 9999;
		    hopcount2 = 9999;
		}
		else
		{
			hopcount = 0;
			hopcount2 = 0;
		}
		age = 0;
		parentPreference = 0;
		testVar = false;
	}
	
	//----------------------------- USED FOR TESTING PURPOSES
	
	public void toggleTest()
	{
		testVar = true;
	}
	
	public boolean getTest()
	{
		return testVar;
	}
	
	public void methodToBeModified()
	{
		System.out.println("Not yet modified!");
	}
	
	//----------------------------- /USED FOR TESTING PURPOSES
	
	public void setParentPreference(Integer pp)
	{
		parentPreference = pp;
	}
	
	public Integer getParentPreference()
	{
		return parentPreference;
	}
	
	public Integer getHopCount()
	{
		return hopcount;
	}
	
	public Integer getSecondHopCount()
	{
		return hopcount2;
	}
	
	public void setHopCount(Integer value)
	{
		hopcount = value;
	}
	
	public void setSecondHopCount(Integer value)
	{
		hopcount2 = value;
	}
	
	public Integer getAge()
	{
		return age;
	}
	
	public void incrementAge()
	{
		age++;
	}
	
	public void setAge(Integer value)
	{
		this.age = value;
	}
	
	public boolean isParent(PeerAddress parent)
	{
		if(parents.contains(parent))
			return true;
		else return false;
	}
	
	public void addParent(PeerAddress parent, Integer HC)
	{
		parents.add(parent);
		parentPLs.put(parent, HC);
	}
	
	public void removeParent(PeerAddress parent)
	{
		parents.remove(parent);
		parentPLs.remove(parent);
	}
	
	public Integer getParentHC(PeerAddress parent)
	{
		return parentPLs.get(parent);
	}
	
	public HashMap<PeerAddress, Integer> getParentHCs()
	{
		return parentPLs;
	}
	
	public void setParentHCs(HashMap<PeerAddress, Integer> phcs)
	{
		parentPLs = phcs;
	}
	
	public void setHopcount(Integer hc)
	{
		hopcount = hc;
	}
	
//	public void setParents(ArrayList<PeerAddress> parents)
//	{
//		this.parents = parents;
//	}
	
	public void setPeerAddress(PeerAddress peeraddress)
	{
		this.peeraddress = peeraddress;
	}
	
	public PeerAddress getPeerAddress()
	{
		return peeraddress;
	}
	
	public ArrayList<PeerAddress> getParents()
	{
		return parents;
	}
	
	public Integer HopCount()
	{
		return hopcount;
	}
	
	public boolean isOrphan()
	{
		if (parents.size() == 0)
				return true;
		else return false;
	}
	
	public Integer highestParentBW()
	{
		Integer max = 0;
		for(int i=0; i<parents.size(); i++)
		{
			if (parents.get(i).getBandwidth()>max)
				max = parents.get(i).getBandwidth();
		}
		return max;
	}
	
	//Need to check individual parent HopCounts - will delegate for later
	public boolean isAcquireable(Integer Bandwidth, Integer HopCount)
	{
		if (parents.size()<Global.id)
			return true;
		else
		{
			for(int i=0; i<parents.size(); i++)
			{
				if(Bandwidth>parents.get(i).getBandwidth())
					return true;
				if(Bandwidth == parents.get(i).getBandwidth())
				{
					try
					{
					if(HopCount < getParentHC(parents.get(i)))
						return true;
					}
					catch(NullPointerException e)
					{
						System.out.println("NULLPOINTER EXCEPTION --> Req parent: " + parents.get(i) + " Hash: " + getParentHCs());
					}
				}
				
			}
			return false;
		}
	}
	
	public boolean HigherThanParentBandwidth(Integer Bandwidth)
	{
		if((parents.size() > 0 && Bandwidth>highestParentBW()))
			return true;
		else return false;
	}
	
	public boolean LowerThanParentHopCount(Integer Bandwidth, Integer HopCount)
	{/*
		if(parents.size() > 0 && Bandwidth == highestParentBW())
		{
			if(HopCount< hopcount)
				return true;
		}
		return false;*/
		
		if(parents.size() > 0 && HopCount<(hopcount-1))
		{
			return true;
		}
		else return false;
	}
	
	public boolean isChild(PeerInfo peer)
	{
		
		for(int i=0; i<peer.getParents().size(); i++)
		{
			if(peer.getParents().get(i).getPeerId() == peeraddress.getPeerId())
			{
				return true;
			}
		}
		return false;
	}
	

	//-------------------------------------------------------------------
	public int compareTo(PeerInfo that) {
		return this.peeraddress.getPeerId().compareTo(that.getPeerAddress().getPeerId());
	}
	
	//-------------------------------------------------------------------
//	public boolean favoredChild(PeerInfo A, PeerInfo B)
//	{
//		Integer myBW = this.getPeerAddress().getBandwidth();
//		Integer BWA = A.getPeerAddress().getBandwidth();
//		Integer BWB = B.getPeerAddress().getBandwidth();
//		ArrayList<PeerAddress> tmpAparents = new ArrayList<PeerAddress>(A.getParents());
//		ArrayList<PeerAddress> tmpBparents = new ArrayList<PeerAddress>(B.getParents());
//		PeerInfo tmpA = new PeerInfo(A.peeraddress, tmpAparents);
//		tmpA.setHopcount(A.getHopCount());
//		tmpA.setSecondHopCount(A.getSecondHopCount());
//		PeerInfo tmpB = new PeerInfo(B.peeraddress, tmpBparents);
//		tmpB.setHopcount(B.getHopCount());
//		tmpB.setSecondHopCount(B.getSecondHopCount());
//		
//		if(tmpA.getPeerAddress().isSource())
//			return false;
//		if(tmpB.getPeerAddress().isSource())
//			return true;
//		if(this.isParent(tmpA.getPeerAddress()))
//			return false;
//		if(this.isParent(tmpB.getPeerAddress()))
//			return true;
//		if(BWA > myBW)
//			return false;
//		if (BWB > myBW)
//			return true;
//		if(myBW == BWA && hopcount>tmpA.getHopCount())
//			return false;
//		if(myBW == BWB && hopcount>tmpB.getHopCount())
//			return true;
//		//HopCount needs to be updated as well; otherwise, current children will have a disadvantage
//		if(isChild(tmpA))
//		{
//			//System.out.println("testing.. 4");
//			tmpA.parents.remove(peeraddress);
//			if(hopcount == tmpA.getHopCount()-1)
//				tmpA.setHopcount(tmpA.getSecondHopCount());
//		}
//		if(isChild(tmpB))
//		{
//			//System.out.println("testing.. 4");
//			tmpB.parents.remove(peeraddress);
//			if(hopcount == tmpB.getHopCount()-1)
//				tmpB.setHopcount(tmpB.getSecondHopCount());
//		}
//		/*
//		if(tmpA.isParent(peeraddress))
//			return false;
//		if(tmpB.isParent(peeraddress))
//			return true;
//			*/
//		if(BWA > BWB)
//		{
//			if(tmpA.isOrphan() || tmpA.LowerThanParentHopCount(myBW, hopcount))
//				return true;
//			if(tmpB.isOrphan() || tmpB.LowerThanParentHopCount(myBW, hopcount))
//				return false;
//			if(tmpA.HigherThanParentBandwidth(myBW))
//				return true;
//			if(tmpB.HigherThanParentBandwidth(myBW))
//				return false;
//			if(!tmpA.isAcquireable(myBW, hopcount) && tmpB.isAcquireable(myBW, hopcount))
//				return false;
//		}
//		
//		else if(BWA == BWB)
//		{
//			if(tmpA.isOrphan() && tmpB.isOrphan())
//			{
//				if(isChild(A))
//					return true;
//				if(isChild(B))
//					return false;
//			}
//			if((tmpA.isOrphan() || tmpA.LowerThanParentHopCount(myBW, hopcount)) && (tmpB.isOrphan() || tmpB.LowerThanParentHopCount(myBW, hopcount)))
//			{
//				if(isChild(A))
//					return true;
//				if(isChild(B))
//					return false;
//			}
//			if(tmpA.isOrphan() || tmpA.LowerThanParentHopCount(myBW, hopcount))
//				return true;
//			if(tmpB.isOrphan() || tmpB.LowerThanParentHopCount(myBW, hopcount))
//				return false;
//			if(tmpA.HigherThanParentBandwidth(myBW) && tmpB.HigherThanParentBandwidth(myBW))
//			{
//				if(isChild(A))
//					return true;
//				if(isChild(B))
//					return false;
//			}
//			if(tmpA.HigherThanParentBandwidth(myBW))
//				return true;
//			if(tmpB.HigherThanParentBandwidth(myBW))
//				return false;
//			if(tmpA.isAcquireable(myBW, hopcount) && tmpB.isAcquireable(myBW, hopcount))
//			{
//				if(isChild(A))
//					return true;
//				if(isChild(B))
//					return false;
//			}
//			if(tmpA.isAcquireable(myBW, hopcount))
//				return true;
//			if(tmpB.isAcquireable(myBW, hopcount))
//				return false;
//		}
//		
//		else if(BWB > BWA)
//		{
//			if(tmpB.isOrphan() || tmpB.LowerThanParentHopCount(myBW, hopcount))
//				return false;
//			if(tmpA.isOrphan() || tmpA.LowerThanParentHopCount(myBW, hopcount))
//				return true;
//			if(tmpB.HigherThanParentBandwidth(myBW))
//				return false;
//			if(tmpA.HigherThanParentBandwidth(myBW))
//				return true;
//			if(!tmpB.isAcquireable(myBW, hopcount) && tmpA.isAcquireable(myBW, hopcount))
//				return true;
//		}
//		
//		return true;
//		
//	}
	
	//-------------------------------------------------------------------
	
	    public boolean favoredParent(PeerInfo A, PeerInfo B)
	    {
	    	if(A.getPeerAddress().getBandwidth() > B.getPeerAddress().getBandwidth())
	    		return true;
	    	else if(A.getPeerAddress().getBandwidth() < B.getPeerAddress().getBandwidth())
	    		return false;
	    	else
	    	{
	    		if(A.getHopCount() < B.getHopCount())
	    			return true;
	    		if(A.getHopCount() > B.getHopCount())
	    			return false;
	    	}
	    	
	    	if(isParent(A.getPeerAddress()))
	    		return true;
	    	if(isParent(B.getPeerAddress()))
	    		return false;
	    	
	    	return true;
	    	
	    }
		public boolean favoredChild(PeerInfo A, PeerInfo B)
		{
			Integer myBW = this.getPeerAddress().getBandwidth();
			Integer BWA = A.getPeerAddress().getBandwidth();
			Integer BWB = B.getPeerAddress().getBandwidth();
			ArrayList<PeerAddress> tmpAparents = new ArrayList<PeerAddress>(A.getParents());
			HashMap<PeerAddress, Integer> tmpAparentHCs = new HashMap<PeerAddress, Integer>(A.getParentHCs());
			ArrayList<PeerAddress> tmpBparents = new ArrayList<PeerAddress>(B.getParents());
			HashMap<PeerAddress, Integer> tmpBparentHCs = new HashMap<PeerAddress, Integer>(B.getParentHCs());
			//Remember to also set Ages if you're going to perform an age comparison
			PeerInfo tmpA = new PeerInfo(A.peeraddress, tmpAparents);
			tmpA.setParentHCs(tmpAparentHCs);
			tmpA.setHopcount(A.getHopCount());
			tmpA.setSecondHopCount(A.getSecondHopCount());
			PeerInfo tmpB = new PeerInfo(B.peeraddress, tmpBparents);
			tmpB.setParentHCs(tmpBparentHCs);
			tmpB.setHopcount(B.getHopCount());
			tmpB.setSecondHopCount(B.getSecondHopCount());
			
			if(tmpA.getPeerAddress().isSource())
				return false;
			if(tmpB.getPeerAddress().isSource())
				return true;
			if(this.isParent(tmpA.getPeerAddress()))
				return false;
			if(this.isParent(tmpB.getPeerAddress()))
				return true;
			if(BWA > myBW)
				return false;
			if (BWB > myBW)
				return true;
			if(myBW == BWA && hopcount>tmpA.getHopCount())
				return false;
			if(myBW == BWB && hopcount>tmpB.getHopCount())
				return true;
			//If node is child, I'll remove myself temporarily from its parents list and adjust its hop-count to be able to make a fair comparison
			if(isChild(tmpA))
			{
				tmpA.parents.remove(peeraddress);
				if(hopcount == tmpA.getHopCount()-1)
					tmpA.setHopcount(tmpA.getSecondHopCount());
			}
			if(isChild(tmpB))
			{
				tmpB.parents.remove(peeraddress);
				if(hopcount == tmpB.getHopCount()-1)
					tmpB.setHopcount(tmpB.getSecondHopCount());
			}

			if(BWA > BWB)
			{
				return Favor(tmpA, tmpB);
			}
			
			else if(BWA == BWB)
			{
				if(isChild(A))
					return Favor(tmpA, tmpB);
				if(isChild(B))
					return !Favor(tmpB, tmpA);
				return Favor(tmpA, tmpB);
			}
			
			else if(BWB > BWA)
			{
				return !Favor(tmpB, tmpA);
			}
			
			return true;
			
		}
		
		//Slightly favors A compared to B
		private boolean Favor(PeerInfo A, PeerInfo B)
		{
			Integer myBW = this.getPeerAddress().getBandwidth();
			if(A.isOrphan() || A.LowerThanParentHopCount(myBW, hopcount))
				return true;
			if(B.isOrphan() || B.LowerThanParentHopCount(myBW, hopcount))
				return false;
			if(A.HigherThanParentBandwidth(myBW))
				return true;
			if(B.HigherThanParentBandwidth(myBW))
				return false;
			if(!A.isAcquireable(myBW, hopcount) && B.isAcquireable(myBW, hopcount))
				return false;
			return true;
		}
	
}
